import operator
import os
import random
import time
from typing import List, Dict, TypedDict, Annotated, Any

from dotenv import load_dotenv
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END

# 加载环境变量
load_dotenv()

# ---------------- 配置区域 ----------------
# 强烈建议使用 GPT-4o，推理能力对裁判打分至关重要
try:
    llm = ChatOpenAI(
        model=os.getenv("LONGMAO_MODEL"),
        base_url=os.getenv("LONGMAO_BASE_URL"),
        api_key=os.getenv("LONGMAO_API_KEY"),
        temperature=0.3  # 裁判需要相对客观冷静，温度调低点
    )
except Exception as e:
    print(f"Error initializing LLM: {e}")
    exit(1)

# 真人玩家的名字
HUMAN_NAME = "我(真人)"


# ---------------- 1. 定义游戏状态 ----------------
class GameState(TypedDict):
    players: List[str]
    player_words: Dict[str, str]
    spy_player: str
    history: Annotated[List[str], operator.add]
    current_turn_idx: int
    round_count: int
    votes: Dict[str, str]
    game_result: str
    cheater_detected: str
    # 【改名】存储 LLM 裁判的评分报告 (格式: {玩家名: "分数|评语"})
    judge_scores: Dict[str, str]


# ---------------- 2. 核心节点逻辑 ----------------

def init_game_node(state: GameState):
    """上帝节点：发牌 (保持不变)"""
    print("\n" + "=" * 40)
    print("🎮 游戏开始！正在分配词汇...")
    word_pairs = [
        ("苹果", "梨"), ("微信", "QQ"), ("麦当劳", "肯德基"),
        ("成吉思汗", "努尔哈赤"), ("蜘蛛侠", "蝙蝠侠"), ("程序员", "黑客")
    ]
    common_word, spy_word = random.choice(word_pairs)
    players = [HUMAN_NAME, "AI_1号", "AI_2号"]
    random.shuffle(players)
    spy = random.choice(players)
    allocation = {}
    for p in players:
        allocation[p] = spy_word if p == spy else common_word

    human_word = allocation[HUMAN_NAME]
    identity = '卧底' if spy == HUMAN_NAME else '平民'
    print(f"🤫 嘘！你的身份是【{identity}】")
    print(f"🔑 你的词是：【{human_word}】 (切记：描述时不能直接包含这个词！)")
    print("-" * 40)
    print(f"👥 玩家发言顺序：{' -> '.join(players)}")
    print("=" * 40 + "\n")
    time.sleep(1)
    return {
        "players": players,
        "player_words": allocation,
        "spy_player": spy,
        "history": [],
        "current_turn_idx": 0,
        "round_count": 1,
        "votes": {},
        "cheater_detected": None,
        "judge_scores": {}
    }


def speaker_node(state: GameState):
    """发言节点 (保持不变)"""
    current_player = state["players"][state["current_turn_idx"]]
    my_word = state["player_words"][current_player]
    spy_player = state["spy_player"]
    round_num = state["round_count"]
    history_str = "\n".join(state['history']) if state['history'] else "（暂无，你是首个发言）"

    print(f"👉 轮到 [{current_player}] 发言...")

    if current_player == HUMAN_NAME:
        print(f"   (提示: 你的词是【{my_word}】)")
        while True:
            content = input(f"🎤 请输入你的描述 > ").strip()
            if not content: continue
            if my_word in content:
                print(f"🚫 犯规警告！不能直接包含关键词。请重试。")
                continue
            break
        msg = f"[{current_player}]: {content}"
    else:
        is_spy = (current_player == spy_player)
        if is_spy:
            prompt = f"""
                        你是{current_player}，身份【卧底】，词【{my_word}】。
                        目标：隐藏自己。第 {round_num} 轮。历史：{history_str}
                        思维链：1.风险评估：我是首发吗？前面人说了什么？2.伪装策略：描述真实特征，但严禁使用独有、精确特征。使用宽泛、模糊或上位概念。
                        输出：仅一句话描述。
                        """
        else:
            prompt = f"""
            你是{current_player}，身份【平民】，词【{my_word}】。
            目标：找卧底。第 {round_num} 轮。历史：{history_str}
            思维链：1.分析局势：谁的描述勉强、模糊或有冲突？2.发言策略：描述一个相对核心特征向队友示好，但不要过于直白。
            输出：仅一句话描述。
            """
        try:
            # print(f"   (🤖 {current_player} 正在思考策略...)")
            response = llm.invoke([HumanMessage(content=prompt)])
            content = response.content.replace(my_word, "***").strip()
            if "最终输出" in content: content = content.split("最终输出")[-1].strip(": \n")
            msg = f"[{current_player}]: {content}"
            print(f"🤖 {msg}")
            time.sleep(random.uniform(0.5, 1.0))
        except Exception as e:
            msg = f"[{current_player}]: (大脑过载...跳过)"
            print(f"❌ AI生成失败: {e}")
    return {"history": [msg]}


def fact_check_node(state: GameState):
    """防作弊节点 (保持不变)"""
    current_player = state["players"][state["current_turn_idx"]]
    last_msg = state["history"][-1]
    real_word = state["player_words"][current_player]
    spoken_text = last_msg.split(":", 1)[1].strip() if ":" in last_msg else last_msg

    prompt = f"""
    裁判任务。词：【{real_word}】。描述："{spoken_text}"
    判断是否违规（FAIL）：1.撒谎/事实错误。2.直接包含词汇或过于直白导致游戏失去意义。
    模糊描述属于正常（PASS）。只回答：PASS 或 FAIL
    """
    try:
        check_res = llm.invoke([HumanMessage(content=prompt)]).content.strip().upper()
        if "FAIL" in check_res:
            print(f"\n🚨 裁判红牌！[{current_player}] 严重违规！直接淘汰！\n")
            return {"cheater_detected": current_player}
    except:
        pass
    return {"current_turn_idx": state["current_turn_idx"] + 1}


# ==================== 【全新】LLM 评分节点 ====================
# ==================== 【全新升级】LLM 评分节点 V3.0 (上帝视角) ====================
def llm_scoring_node(state: GameState):
    """
    LLM 评分节点 V3.0：上帝视角裁判。
    裁判同时知道平民词和卧底词，能更精准地识别侧重点偏移。
    """
    print("\n🧑‍🏫 === 正在等待裁判团进行语义评估(上帝视角) ===")

    num_players = len(state["players"])
    current_turn_history = state["history"][-num_players:]

    spy_player = state["spy_player"]
    # 获取双方词汇
    if spy_player != HUMAN_NAME:
        common_word = state["player_words"][HUMAN_NAME]
        spy_word = state["player_words"][spy_player]
    else:
        # 如果真人是卧底，随便取一个 AI 的词做平民词
        common_word = state["player_words"]["AI_1号"]
        spy_word = state["player_words"][HUMAN_NAME]

    # 打印出来给自己调试看，玩家是看不到控制台的
    print(f"   (裁判内幕：平民词【{common_word}】 vs 卧底词【{spy_word}】)")

    scores_report = {}

    for msg in current_turn_history:
        player_name, content = msg.split(":", 1)
        player_name = player_name.strip("[]")
        content = content.strip()

        # --- 裁判打分的核心 Prompt (V3.0: 上帝视角+安全禁令) ---
        prompt = f"""
        你是一位《谁是卧底》游戏的专业裁判。
        **绝密内幕信息（玩家不可见）**：
        - 标准平民词是：【{common_word}】
        - 干扰卧底词是：【{spy_word}】

        玩家 [{player_name}] 的描述是：“{content}”

        请对这个描述进行评分（0-100分）并给出简短评语。

        **评分核心逻辑（Crucial）**：
        你的打分基准依然是描述与**【{common_word}】**的契合度。但是，你要利用已知【{spy_word}】的信息来敏锐地捕捉“侧重点偏移”。

        **详细标准**：
        1. **高分区 (85-100)**：精准描述了【{common_word}】的核心特征，且不易与卧底词混淆。
        2. **中分区 (65-84)**：描述符合【{common_word}】，但使用的是与【{spy_word}】共有的宽泛边缘特征。
        3. **低分/重罚区 (0-64)**：
           - **极度划水**：信息量极低（如“它是个东西”）。
           - **【关键】侧重点严重偏移**：如果描述的特征虽然跟平民词沾边，但明显更偏向于**【{spy_word}】**的核心特征，必须予以重罚（打低分）！

        **【绝对安全禁令】（违反直接判负）**：
        - 你的评语是公开给所有玩家看的（包括不知道底牌的真人）。
        - **严禁在评语中直接提及【{spy_word}】这个词本身！泄密是最大的事故！**
        - 如果描述偏向卧底词，评语只能用含蓄的表达，例如：“侧重点严重偏移”、“描述了非核心特征”、“感觉在描述另一个相似概念”、“特征不典型”等。

        **输出格式**：`分数|简短评语(15字内，不可泄密)`
        """

        try:
            res = llm.invoke([HumanMessage(content=prompt)])
            result_str = res.content.strip()
            if "|" not in result_str: result_str = "50|裁判未给出具体评价"
            score_val = int(result_str.split("|")[0])
            comment = result_str.split("|")[1]
            scores_report[player_name] = result_str

            bar_len = int(score_val / 5)
            bar = "█" * bar_len + "░" * (20 - bar_len)
            print(f"   📝 [{player_name}] 得分: {score_val:3d} [{bar}] 评语: {comment}")

        except Exception as e:
            print(f"   ⚠️ 裁判对 [{player_name}] 评分失败: {e}")
            scores_report[player_name] = "50|评分失败"

    print("============================================\n")
    return {"judge_scores": scores_report}


# ============================================================


def voting_node(state: GameState):
    """投票节点 (已更新，包含 V2.1 卧底策略, V6.0 平民策略, 以及推理失败时的调试打印)"""
    print(f"\n🗳️ === 第 {state['round_count']} 轮描述结束，开始投票 ===")
    print("-" * 30)
    players = state["players"]
    spy_player = state["spy_player"]
    history_str = "\n".join(state["history"])

    # 【重点】从 state 中取出 LLM 裁判的评分报告
    scores_report = state.get("judge_scores", {})
    # 将报告格式化为字符串以便放入 Prompt
    report_str = "\n".join([f"- {name}: {score}" for name, score in scores_report.items()])

    new_votes = {}
    for p in players:
        if p == HUMAN_NAME:
            # (真人投票逻辑不变)
            print("💀 请根据上面的历史发言，找出卧底。")
            print(f"   候选人: {players}")
            while True:
                vote_target = input(f"🗳️ 你投给谁? (输入名字的关键词) > ").strip()
                found = False
                for valid in players:
                    if vote_target and (valid in vote_target or vote_target in valid):
                        vote_target = valid
                        found = True
                        break
                if found: break
                print("❌ 找不到该玩家。")
            print(f"✅ 你投给了: {vote_target}")
        else:
            my_word = state["player_words"][p]
            is_spy = (p == spy_player)

            if is_spy:
                # ================= 🕵️‍♂️ 卧底投票策略 V2.1：基于事实的诡辩 (修复幻觉) =================
                prompt = f"""
                 你是{p}，身份【卧底】，词【{my_word}】。目标：找替罪羊。

                 历史发言：{history_str}
                 裁判报告：{report_str}

                 思维链：
                 1. **确定目标**：选择一个对你威胁最大的平民（通常是得分最高的）。
                 2. **定制谎言（关键约束）**：
                    - 你可以曲解他的发言，或者歪曲裁判的评语。
                    - **【严禁】无中生有！** 你编造的谎言必须基于他实际说过的话，或者裁判报告里实际存在的文字。不能编造裁判没说过的话（例如不能编造“裁判说他导致项目延误”）。
                    - **推荐策略**：如果目标得分很高，你可以攻击他“表现得太完美了，像是在背书演戏”。

                 输出要求：严禁思维链。只输出 `被投人名字|简短理由(基于事实的歪曲)`
                 """
            else:
                # ================= 🧑‍🤝‍🧑 平民投票策略 V6.0 (行为逻辑审查) =================
                prompt = f"""
                你是 {p}，身份【平民】，词【{my_word}】。目标：找卧底。

                **【权威参考】裁判团评分报告：**
                {report_str}

                请执行以下思维链进行推理：

                ### 第一步：初步筛选（基于裁判分数）
                1. **低分嫌疑区**：关注分数较低（<65）且评语指出“模糊”、“划水”的人。这是传统卧底的嫌疑点。
                2. **高分观察区**：分数较高（>80）的人通常是队友，但需要进行第二步审查。

                ### 第二步：关键行为逻辑审查（Crucial!）
                **这是找出高级卧底的关键！** 即使某人得分很高（说明事实正确），也要审查他的描述是否符合常理：
                - **反常识行为**：如果有人声称自己在使用【{my_word}】时，**刻意避开**了其最最主要的功能（例如：用微信却说自己很少聊天），这种行为极度可疑！
                - **过度做作**：描述过于书面化、像广告词。

                ### 第三步：综合决策
                - **最高优先级**：如果发现了符合【第二步】中“反常识行为”的高分玩家，不要犹豫，优先投他！
                - **次优先级**：如果没有明显的反常识行为，则把票投给得分最低、评语最差的那个人。
                - **【兜底逻辑】（新增）：如果所有人的分数都很高（例如都>85），且没有明显的反常识行为。此时难以判断，请在得分相对较低的几个人中，随机选择一个，并给出理由：“全场表现都很好，暂无明确嫌疑目标，随机选择。”**

                **要求**：
                - **严禁输出任何思维链过程！**
                - **只输出最终的投票结果**，格式必须是：`被投人名字|简短理由`。
                """

            # AI 执行推理和投票
            try:
                # print(f"   (🤖 {p} 正在参考裁判报告进行推理...)")
                res = llm.invoke([HumanMessage(content=prompt)])
                content = res.content.strip()
                reason = "无理由"

                # 先保存原始的 target，用于调试
                raw_target = content
                vote_target = raw_target

                if "|" in content:
                    parts = content.split("|", 1)
                    vote_target = parts[0].strip()
                    reason = parts[1].strip()
                    raw_target = vote_target  # 更新原始target

                found = False
                for valid in players:
                    if valid in vote_target and valid != p:
                        vote_target = valid
                        found = True
                        break

                # 【重点修改】如果没找到有效目标，打印调试信息，再进行随机
                if not found:
                    print(f"   ⚠️ [{p}] 推理出的目标【{raw_target}】无效（不含有效玩家名），被迫转为随机投票。")
                    others = [pl for pl in players if pl != p]
                    vote_target = random.choice(others) if others else p
                    reason = "随机投票(AI推理出无效目标)"

                print(f"   🤖 [{p}] 投给了 -> {vote_target} (理由: {reason})")
            except Exception as e:
                print(f"   ⚠️ [{p}] 投票程序出错: {e}，被迫弃票。")
                vote_target = "弃票"
        new_votes[p] = vote_target
    print("-" * 30)
    return {"votes": new_votes}


def judge_node(state: GameState):
    """判决节点 (逻辑不变)"""
    if state.get("cheater_detected"):
        cheater = state["cheater_detected"]
        spy = state["spy_player"]
        if cheater == spy:
            return {"game_result": f"卧底[{cheater}]违规被罚下！平民胜利！"}
        else:
            return {"game_result": f"平民[{cheater}]违规被罚下！但这局算平民输。"}

    votes = state["votes"]
    counts = {}
    for target in votes.values():
        if target != "弃票": counts[target] = counts.get(target, 0) + 1
    print(f"📊 最终票数统计: {counts}")

    if not counts:
        return {"round_count": state["round_count"] + 1, "current_turn_idx": 0}

    sorted_votes = sorted(counts.items(), key=lambda x: x[1], reverse=True)
    top_votes = sorted_votes[0][1]
    top_players = [p for p, v in sorted_votes if v == top_votes]

    if len(top_players) > 1:
        print(f"⚖️ 平票！{top_players}，进入下一轮！\n")
        return {"round_count": state["round_count"] + 1, "current_turn_idx": 0}

    out_player = top_players[0]
    print(f"\n❌ [{out_player}] 被淘汰！")
    spy = state["spy_player"]
    print(
        f"🔑 真相：卧底是[{spy}]，词【{state['player_words'][spy]}】。平民词【{state['player_words'][HUMAN_NAME] if spy != HUMAN_NAME else state['player_words']['AI_1号']}】")

    if out_player == spy:
        result = f"\n🎉 平民胜利！"
    else:
        result = f"\n😈 卧底胜利！"
    return {"game_result": result}


# ---------------- 3. 构建图与路由 ----------------

def turn_router(state: GameState):
    if state.get("cheater_detected"): return "judge"
    if state["current_turn_idx"] < len(state["players"]): return "speak"
    return "analyze"  # 说完去分析


def game_router(state: GameState):
    if state.get("game_result"): return "end"
    return "continue"


workflow = StateGraph(GameState)

workflow.add_node("Init", init_game_node)
workflow.add_node("Speaker", speaker_node)
workflow.add_node("FactCheck", fact_check_node)
# 【改名】添加 LLM 评分节点
workflow.add_node("LLMScoring", llm_scoring_node)
workflow.add_node("Voting", voting_node)
workflow.add_node("Judge", judge_node)

workflow.set_entry_point("Init")
workflow.add_edge("Init", "Speaker")
workflow.add_edge("Speaker", "FactCheck")

workflow.add_conditional_edges(
    "FactCheck",
    turn_router,
    {
        "speak": "Speaker",
        "analyze": "LLMScoring",  # 【改名】路由指向新节点
        "judge": "Judge"
    }
)

# 【改名】分析完 -> 投票
workflow.add_edge("LLMScoring", "Voting")
workflow.add_edge("Voting", "Judge")

workflow.add_conditional_edges(
    "Judge",
    game_router,
    {
        "continue": "Speaker",
        "end": END
    }
)

app = workflow.compile()

# ---------------- 4. 启动游戏 ----------------
if __name__ == "__main__":
    try:
        app.invoke({"players": []})
    except KeyboardInterrupt:
        print("\n🛑 游戏已手动终止。")
    except Exception as e:
        import traceback

        traceback.print_exc()